<!DOCTYPE html>
<html>
  <head>
    <!-- Load JQuery and JQuery-UI -->
    <link type="text/css" href="css/hot-sneaks/jquery-ui-1.8.custom.css" rel="stylesheet" />  
    <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="js/jquery-ui-1.8.custom.min.js"></script>
    
    <!-- Load Processing.js -->
    <script language="javascript" src="js/processing.js"></script>
    <script language="javascript" src="js/init.js"></script>

    <!-- Load DSP.js -->
    <script src="../dsp.js"></script>
    <script>
    </script>

    <style type="text/css">
    </style>
  </head>
  <body>
    <script>
      // Setup shared variables
      var sampleRate = 44100;
      var bufferSize = 2048;
      var bufferTime = Math.floor(1000 / (sampleRate / bufferSize));
      
      var amplitude  = 0.1; // Default amplitude at 70%

      var oct = 2; // Root Octave

      var oscs = [];
      var noteOn = [];
      var playSample = false;

      // Borrowed from F1LTER's code
      var midiNoteFreq =  [ 16.35,    17.32,    18.35,    19.45,    20.6,     21.83,    23.12,    24.5,     25.96,    27.5,  29.14,    30.87,
                          32.7,     34.65,    36.71,    38.89,    41.2,     43.65,    46.25,    49,       51.91,    55,    58.27,    61.74,
                          65.41,    69.3,     73.42,    77.78,    82.41,    87.31,    92.5,     98,       103.83,   110,   116.54,   123.47,
                          130.81,   138.59,   146.83,   155.56,   164.81,   174.61,   185,      196,      207.65,   220,   233.08,   246.94,
                          261.63,   277.18,   293.66,   311.13,   329.63,   349.23,   369.99,   392,      415.3,    440,   466.16,   493.88,
                          523.25,   554.37,   587.33,   622.25,   659.26,   698.46,   739.99,   783.99,   830.61,   880,   932.33,   987.77,
                          1046.5,   1108.73,  1174.66,  1244.51,  1318.51,  1396.91,  1479.98,  1567.98,  1661.22,  1760,  1864.66,  1975.53,
                          2093,     2217.46,  2349.32,  2489.02,  2637.02,  2793.83,  2959.96,  3135.96,  3322.44,  3520,  3729.31,  3951.07,
                          4186.01,  4434.92,  4698.64,  4978 ];

      var silence = new Float32Array(bufferSize);

      // Setup audio channel
      var output = new Audio();
      if ( typeof output.mozSetup === 'function' ) {
        output.mozSetup(1, sampleRate, 1);
      }
      
      var timePerWrite = 0;
      var programStart;
      
      // load the voice formant
      var v = new Sampler('audio/formant.ogg', bufferSize, sampleRate);
      v.envelope = new ADSR(0, 0, 1, Infinity, 0, sampleRate);
      v.envelope.disable(); // turn off so it does not auto trigger

      // load the synth carrier
      var s = new Sampler('audio/carrier.ogg', bufferSize, sampleRate);
      s.envelope = new ADSR(0, 0, 1, Infinity, 0, sampleRate);
      s.envelope.disable(); // turn off so it does not auto trigger
      s.loopMode = 3; // back and forth loop

      var fftcar = new FFT(bufferSize, sampleRate);
      var fftmod = new FFT(bufferSize, sampleRate);

      var hann = new WindowFunction(DSP.HANN);

      var real = new Float32Array(bufferSize);
      var imag = new Float32Array(bufferSize);

      var spec1, spec2;

      var numBands = 32;
      var bands = [];

      for (var i = 0; i < bufferSize/2; i+= Math.round((bufferSize/2)/numBands) ) {
        bands.push(i);  
      }
      bands[bands.length-1] = bufferSize/2;

      var audioWriter = function() {
        if (s.loaded) {
          var startTime = (new Date()).getTime();        
        
          if ( programStart === undefined ) {
             programStart = new Date();
          }
        
          var carrier, formant, additiveSignal = [];
        
          if ( s.envelope.isActive() && v.envelope.isActive() ) {
            s.generate();
            v.generate();
            carrier = s.applyEnvelope();
            formant = v.applyEnvelope();

            spec1 = fftcar.forward(hann.process(carrier));
            spec2 = fftmod.forward(hann.process(formant));

            for (var i = 0; i < numBands-1; i++) {
              var totalReal = 0;
              var totalImag = 0;
              var count = 0;
              for (var b = bands[i]; b < bands[i+1]; b++) {
                totalReal += Math.abs(fftmod.real[b]);  
                totalImag += Math.abs(fftmod.imag[b]);  
                count++;
              }
              var meanReal = totalReal/count;
              var meanImag = totalImag/count;
              for (var b = bands[i]; b < bands[i+1]; b++) {
                real[b] = fftcar.real[b] * meanReal;  
                imag[b] = fftcar.imag[b] * meanImag;
              }
            }

            additiveSignal = fftcar.inverse(real, imag);

            /*
            for (var i = 0; i < additiveSignal.length; i++) {
              if (additiveSignal[i] > 0.8) {
                additiveSignal[i] = 0.7;
              } else if (additiveSignal[i] < -0.8) {
                additiveSignal[i] = -0.7;
              }
              
            }
            */
            
          } else if ( s.envelope.isActive() ) {
            s.generate();
            additiveSignal = s.applyEnvelope();
          } else if ( v.envelope.isActive() ) {
            v.generate();
            additiveSignal = v.applyEnvelope();
          }
        
          // Set the global osc object
          if ( typeof  additiveSignal === 'undefined' ) {
            additiveSignal = silence;
          }

          // Flush buffer  
          output.mozWriteAudio([]);
        
          // Write next audio frame
          output.mozWriteAudio(additiveSignal);

          var endTime = (new Date()).getTime();
        
          timePerWrite = endTime - startTime;
        }
      }
    </script>
      
    <script target="#keyboard" type="application/processing">
      void setup() {
        size(1024, 400);
        
        setInterval(audioWriter, bufferTime); // start audioWriter timer      
        stroke(255);
        frameRate(20);
      }

      void draw() { 
        background(255);
        fill(0)
        stroke(255);
        if (!s.loaded) {
          var loadPercent = s.samples.length / (s.duration * sampleRate);
          text("Loading carrier: " + Math.round(loadPercent * 100) + "%", 0, 20);   
        }
        if (!v.loaded) {
          var loadPercent = v.samples.length / (v.duration * sampleRate);
          text("Loading formant: " + Math.round(loadPercent * 100) + "%", 0, 40);   
        }

        if ( s.loaded && v.loaded ) {
          /*
          // Draw formant:
          stroke(0, 0, 255);
          for (int i = 0; i < v.samples.length; i+=100) {
             line(i/100, 50-v.samples[i]*50, i/100, 50+v.samples[i]*50); 
          }
          // Draw carrier
          stroke(255, 0, 0);
          for (int i = 0; i < s.samples.length; i+=100) {
             line(i/100, 150-s.samples[i]*50, i/100, 150+s.samples[i]*50); 
          }
          */

          noStroke();
          fill(0);
          for (var i = 0; i < bufferSize/4; i+= 4) {
            if (real[i] && imag[i]) {
              rect(i, 100, 2, - fftmod.spectrum[i] * 1000);
            }
          }
          fill(0);
          for (var i = 0; i < bufferSize/4; i+= 4) {
            if (real[i] && imag[i]) {
              rect(i, 200, 2, - fftcar.spectrum[i] * 1000);
            }
          }
          fill(255, 0, 0);
          for (var i = 0; i < bufferSize/4; i+= 4) {
            if (real[i] && imag[i]) {
              rect(i, height, 2, - (Math.sqrt(real[i] * real[i] + imag[i] * imag[i])) / bufferSize * 1000);
            }
          }
        }
      }

      void keyPressed() {
        var semi = undefined;
        
        if ( key == 'z' ) {   // C
          semi = 0;
        }
        
          if ( key == 's' ) { // C#
            semi = 1;
          }
          
        if ( key == 'x' ) {   // D
          semi = 2;
        }
        
          if ( key == 'd' ) { // D#
            semi = 3;
          }
        
        if ( key == 'c' ) {   // E
          semi = 4;
        }
        
        if ( key == 'v' ) {   // F
          semi = 5;
        }
        
          if ( key == 'g' ) { // F#
            semi = 6;
          }
          
        if ( key == 'b' ) {   // G
          semi = 7;
        }
        
          if ( key == 'h' ) { // G#
            semi = 8;
          }
        
        if ( key == 'n' ) {   // A
          semi = 9;
        }
        
          if ( key == 'j' ) { // A#
            semi = 10;
          }
        
        if ( key == 'm' ) {   // B
          semi = 11;
        }
        
        if ( key == '=') { // increase octave
          oct++;
        }
        
        if ( key == '-') { // descrease octave 
          oct--;
        }
        
        if (typeof semi !== 'undefined' && s.loaded && v.loaded) {
          s.envelope.noteOn();
          s.setFreq(midiNoteFreq[12*oct+semi]);
          v.envelope.noteOn();
          v.setFreq(midiNoteFreq[12*oct+semi]);
        }

        if (key == 'w') {
          s.envelope.noteOn();
          s.setFreq(midiNoteFreq[12*oct]);
        }
        if (key == 'q') {
          v.envelope.noteOn();
          v.setFreq(midiNoteFreq[12*oct]);
        }
        if (key == 'e') {
          s.envelope.noteOn();
          s.setFreq(midiNoteFreq[12*oct]);
          v.envelope.noteOn();
          v.setFreq(midiNoteFreq[12*oct]);
        }
      }
      
      void keyReleased() {
        if ( s.envelope.isActive() ) {
          s.envelope.noteOff();
          s.reset();
        }
        if ( v.envelope.isActive() ) {
          v.envelope.noteOff();
          v.reset();
        }
      }
    </script>
    <h1>Vocoder</h1>
    <canvas id="keyboard" width="200px" height="200px"></canvas>
    
    <h2>Playing sample</h2>
    <p><b>Q</b> Play the voice formant sample</p>
    <p><b>W</b> Play the carrier synth sample</p>
    <p><b>E</b> Play the vocoded sample</p>
    <p>Bottom row keys z to m are the white keys of one octave of a piano.</p>
    <p>Middle row keys s,d  and g,h,j are the black keys of one octave of a piano</p>
    <p>- and = decrease or increase the octave</p>
  </body>
</html>
